import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;

// 创建任务类，表示定时器中要执行的任务
class MyTimeTask implements Comparable<MyTimeTask>{
    // 具体的任务
    private Runnable runnable;
    // 任务在何时被执行. time 填写 ms 级别的时间戳
    // 目的是为了方便判定，是否到达执行的时间了
    private long time;

    // 此处是 delay 是一个相对的"时间间隔"，以当前时刻为基准，代表 delay ms 后的时刻
    public MyTimeTask(Runnable runnable, long delay){
        this.runnable = runnable;
        // 记录绝对时间，到了这个时间后就开始执行
        this.time = System.currentTimeMillis() + delay;
    }

    // 获取到任务在何时被执行
    public long getTime(){
        return time;
    }

    // 执行任务
    public void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTimeTask o) {
        // 返回整数，拿着 this 和 other 进行对比
        // 如果 this < other，返回 < 0；如果 this > other，返回 > 0；如果 this ==  other，返回 0
        // 目标是为了让 time 最小的在队首.
        // 谁减去谁, 不要背，也不要记，也不要试图找规律. 应该是做个实验.
        return (int)(this.time - o.time);
    }
}

// 自定义的定时器类
class MyTimer {
    // 使用集合类, 把多个安排的任务给保存起来.
    // 把这些任务按照时间排序，后续判定的时候，只需考虑第一个元素时间到没到
    // PriorityQueue 按照时间作为优先级，让时间靠前的排到前面去. 每次出队列/取队首都是时间最靠前的元素
    private PriorityQueue<MyTimeTask> queue = new PriorityQueue<>();
    private Object locker = new Object();

    public MyTimer(){
        // 创建新线程，线程负责执行这里的逻辑
        Thread t = new Thread(() -> {
            try {
                while (true) {
                    // 主线程往queue放，t 线程从queue取，这是两个不同的线程
                    // 可能触发线程安全问题，所以要加锁
                    // 此处锁要加在while循环里面，如果锁包含while循环的话，整个while循环就一直持有锁，那就schedule不了了
                    synchronized (locker){
                        // 1. 取出队首元素
                        MyTimeTask task = queue.peek();
                        while (task == null) {
                            // 使用 wait 代替 continue, 避免出现忙等
                            // 何时通知呢？应该在队列不空 其他线程进行schedule的时候
                            locker.wait();
                            // wait完之后，重新获取一下当前的队首，再看新的队首是否为空
                            task = queue.peek();
                            /*后续有新逻辑的时候，需要用 while 确认一下*/
                        }
                        // 2. 判定时间是否到达
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            // 时间到了，需要执行了
                            task.run();
                            // 执行完毕, 任务移除
                            queue.poll();
                        } else {
                            // 时间没到
                            locker.wait(task.getTime() - curTime);
                            // 要么时间到了 自然醒；要么 每次添加新任务的时候，也需要唤醒
                            // 如果新添加的任务，任务时间是9：00，下次取出的队首元素还是8：00任务（不唤醒也没事）
                            // 如果新添加的任务，任务时间是7：30，就需要唤醒之前的 wait，重新设定等待时间
                            /*这个 wait 完成之后，下一步做的正好就是重新确认任务时间*/
                        }
                    }
                }
            }catch (InterruptedException e) {
                throw new RuntimeException("wait 被终止");
            }
        });
        t.start();
    }

    public void schedule(Runnable runnable, long delay){
        synchronized (locker){
            MyTimeTask task = new MyTimeTask(runnable,delay);
            queue.add(task);
            locker.notify();
        }
    }
}


public class Demo2 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();

        myTimer.schedule(() -> {
            System.out.println("hello myTimer 3000");
        },3000);

        myTimer.schedule(() -> {
            System.out.println("hello myTimer 2000");
        },2000);

        myTimer.schedule(() -> {
            System.out.println("hello myTimer 1000");
        },1000);
    }


    public static void main1(String[] args) throws InterruptedException {
        // Timer 类内部有专门的线程执行任务，内置的线程是前台线程
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello timer 3000");
            }
        },3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello timer 2000");
            }
        },2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello timer 1000");
            }
        },1000);

        Thread.sleep(4000);

        timer.cancel();
    }
}
